package org.i0o.utilplus.populator;

import java.beans.BeanInfo;
import java.beans.IndexedPropertyDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.DynaClass;
import org.apache.commons.beanutils.DynaProperty;
import org.apache.commons.beanutils.MappedPropertyDescriptor;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.commons.beanutils.expression.Resolver;
import org.i0o.utilplus.populator.converters.BigDecimalConverter;
import org.i0o.utilplus.populator.converters.BooleanConverter;
import org.i0o.utilplus.populator.converters.DateTimeConverter;
import org.i0o.utilplus.populator.converters.DoubleConverter;
import org.i0o.utilplus.populator.converters.FloatConverter;
import org.i0o.utilplus.populator.converters.IntegerConverter;
import org.i0o.utilplus.populator.converters.LongConverter;
import org.i0o.utilplus.populator.converters.ShortConverter;
import org.i0o.utilplus.populator.converters.StringConverter;

/**
 * 转换器工具类
 * 
 * @author <a href="mailto:781131@gmail.com">HTF</a>
 * @version 2010-5-26
 */
public final class ConvertUtils {

	private static final Map<Class<?>, IConverter> converter;

	private static PropertyUtilsBean propertyUtilsBean;
	static {
		Map<Class<?>, IConverter> convertList = new HashMap<Class<?>, IConverter>();

		// Primitive
		convertList.put(Boolean.TYPE, new BooleanConverter());
		convertList.put(Integer.TYPE, new IntegerConverter());
		convertList.put(Long.TYPE, new LongConverter());
		convertList.put(Float.TYPE, new FloatConverter());
		convertList.put(Double.TYPE, new DoubleConverter());
		convertList.put(Short.TYPE, new ShortConverter());
		convertList.put(Character.TYPE, null);
		convertList.put(Byte.TYPE, null);

		// object
		convertList.put(Boolean.class, new BooleanConverter());
		convertList.put(Integer.class, new IntegerConverter());
		convertList.put(Long.class, new LongConverter());
		convertList.put(Float.class, new FloatConverter());
		convertList.put(Double.class, new DoubleConverter());
		convertList.put(Short.class, new ShortConverter());
		convertList.put(Character.class, null);
		convertList.put(Byte.class, null);
		convertList.put(String.class, new StringConverter());
		convertList.put(BigInteger.class, new StringConverter());
		convertList.put(BigDecimal.class, new BigDecimalConverter());

		// orth
		convertList.put(java.util.Date.class, new DateTimeConverter());
		convertList.put(java.sql.Date.class, new DateTimeConverter());
		convertList.put(java.sql.Timestamp.class, new DateTimeConverter());
		convertList.put(java.util.Calendar.class, new DateTimeConverter());

		converter = convertList;
	}

	private static IConverter lookup(Class<?> targetType) {
		return converter.get(targetType);
	}

	/**
	 * 根据转换类型获取相应的转换器
	 * 
	 * @param sourceType 原类型
	 * @param targetType 目标类型
	 * @return 转换器实例
	 */
	private static IConverter lookup(Class<?> sourceType, Class<?> targetType) {

		if (sourceType == null) {
			return lookup(targetType);
		}

		IConverter converter = null;
		if (targetType == String.class) {
			converter = lookup(sourceType);

			if (converter == null) {
				converter = lookup(String.class);
			}
			return converter;
		}

		return lookup(targetType);
	}

	/**
	 * @param value
	 * @param targetType
	 * @return
	 */
	public static Object convert(Object value, Class<?> targetType, Object... configs) {
		Class<?> sourceType = value == null ? null : value.getClass();

		return lookup(sourceType, targetType).convert(value, targetType, configs);
	}

	/**
	 * 将对象转换成字符串
	 * 
	 * @param value 值对象
	 * @return
	 */
	public static String convertToString(Object value) {
		return (String) ConvertUtils.convert(value, String.class);
	}

	public static Integer convertToInteger(Object value) {
		return (Integer) ConvertUtils.convert(value, Integer.class);
	}

	public static Long convertToLong(Object value) {
		return (Long) ConvertUtils.convert(value, Long.class);
	}

	public static Double convertToDouble(Object value) {
		return (Double) ConvertUtils.convert(value, Double.class);
	}

	public static Float convertToFloat(Object value) {
		return (Float) ConvertUtils.convert(value, Float.class);
	}

	public static Date convertToDate(Object value) {
		return (Date) ConvertUtils.convert(value, Date.class);
	}

	/**
	 * 类型转换
	 * 
	 * @param bean
	 * @param properties
	 */
	@SuppressWarnings("rawtypes")
	public static void populate(Object bean, Map properties) {
		if ((bean == null) || (properties == null)) {
			return;
		}

		// Loop through the property name/value pairs to be set
		Iterator<?> entries = properties.entrySet().iterator();
		while (entries.hasNext()) {

			// Identify the property name and value(s) to be assigned
			Map.Entry entry = (Map.Entry) entries.next();
			String name = (String) entry.getKey();
			if (name == null) {
				continue;
			}
			try {
				// Perform the assignment for this property
				setProperty(bean, name, entry.getValue());
			} catch (Exception e) {
				// throw new BusinessLayerException("Set property Exception!",
				// e);
			}

		}
	}

	/**
	 * 复制Bean属性
	 * 
	 * @param dest 原始bean
	 * @param orig 目标bean
	 */
	public static void copyProperties(Object orig, Object dest) {
		if (dest == null) {
			throw new IllegalArgumentException("No destination bean specified");
		}
		if (orig == null) {
			throw new IllegalArgumentException("No origin bean specified");
		}

		PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig.getClass());
		for (PropertyDescriptor descriptor : origDescriptors) {
			String name = descriptor.getName();
			if ("class".equals(name)) {
				continue;
			}

			Object value = getValue(orig, name);
			try {
				setProperty(dest, name, value);
			} catch (Exception e) {
				// 日志，错误忽略
			}
		}

	}

	/**
	 * 获取值
	 * 
	 * @param bean
	 * @param name
	 * @return
	 */
	public static Object getValue(Object bean, String name) {
		Object obj = null;
		try {
			PropertyDescriptor descriptor = new PropertyDescriptor(name, bean.getClass());
			Method readMethod = descriptor.getReadMethod();
			if (readMethod != null) {
				obj = readMethod.invoke(bean);
			}

		} catch (Exception e) {
			return null;
		}
		return obj;
	}

	/**
	 * 获取类的属性描述数组
	 * 
	 * @param beanClass 类
	 * @return
	 */
	public static PropertyDescriptor[] getPropertyDescriptors(Class<?> beanClass) {
		if (beanClass == null) {
			throw new IllegalArgumentException("No bean class specified");
		}
		PropertyDescriptor[] descriptors = null;
		BeanInfo beanInfo = null;
		try {
			beanInfo = Introspector.getBeanInfo(beanClass);
		} catch (IntrospectionException e) {
			return (new PropertyDescriptor[0]);
		}

		descriptors = beanInfo.getPropertyDescriptors();
		if (descriptors == null) {
			descriptors = new PropertyDescriptor[0];
		}

		return descriptors;
	}

	@SuppressWarnings("rawtypes")
	private static void setProperty(Object bean, String name, Object value) throws IllegalAccessException, InvocationTargetException {

		// Trace logging (if enabled)

		// Resolve any nested expression to get the actual target bean
		Object target = bean;
		Resolver resolver = getPropertyUtils().getResolver();
		while (resolver.hasNested(name)) {
			try {
				target = getPropertyUtils().getProperty(target, resolver.next(name));
				name = resolver.remove(name);
			} catch (NoSuchMethodException e) {
				return; // Skip this property setter
			}
		}

		// Declare local variables we will require
		String propName = resolver.getProperty(name); // Simple name of target
														// property
		Class type = null; // Java type of target property
		int index = resolver.getIndex(name); // Indexed subscript value (if any)
		String key = resolver.getKey(name); // Mapped key value (if any)

		// Calculate the property type
		if (target instanceof DynaBean) {
			DynaClass dynaClass = ((DynaBean) target).getDynaClass();
			DynaProperty dynaProperty = dynaClass.getDynaProperty(propName);
			if (dynaProperty == null) {
				return; // Skip this property setter
			}
			type = dynaProperty.getType();
		} else if (target instanceof Map) {
			type = Object.class;
		} else if (target != null && target.getClass().isArray() && index >= 0) {
			type = Array.get(target, index).getClass();
		} else {
			PropertyDescriptor descriptor = null;
			try {
				descriptor = getPropertyUtils().getPropertyDescriptor(target, name);
				if (descriptor == null) {
					return; // Skip this property setter
				}
			} catch (NoSuchMethodException e) {
				return; // Skip this property setter
			}
			if (descriptor instanceof MappedPropertyDescriptor) {
				if (((MappedPropertyDescriptor) descriptor).getMappedWriteMethod() == null) {

					return; // Read-only, skip this property setter
				}
				type = ((MappedPropertyDescriptor) descriptor).getMappedPropertyType();
			} else if (index >= 0 && descriptor instanceof IndexedPropertyDescriptor) {
				if (((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod() == null) {
					return; // Read-only, skip this property setter
				}
				type = ((IndexedPropertyDescriptor) descriptor).getIndexedPropertyType();
			} else if (key != null) {
				if (descriptor.getReadMethod() == null) {

					return; // Read-only, skip this property setter
				}
				type = (value == null) ? Object.class : value.getClass();
			} else {
				if (descriptor.getWriteMethod() == null) {
					return; // Read-only, skip this property setter
				}
				type = descriptor.getPropertyType();
			}
		}

		// Convert the specified value to the required type
		Object newValue = null;
		if (type.isArray() && (index < 0)) { // Scalar value into array
			if (value == null) {
				String[] values = new String[1];
				values[0] = null;
				newValue = ConvertUtils.convert(values, type);
			} else if (value instanceof String) {
				newValue = ConvertUtils.convert(value, type);
			} else if (value instanceof String[]) {
				newValue = convert((String[]) value, type);
			} else {
				newValue = convert(value, type);
			}
		} else if (type.isArray()) { // Indexed value into array
			if (value instanceof String || value == null) {
				newValue = convert((String) value, type.getComponentType());
			} else if (value instanceof String[]) {
				newValue = convert(((String[]) value)[0], type.getComponentType());
			} else {
				newValue = convert(value, type.getComponentType());
			}
		} else { // Value into scalar
			if (value instanceof String) {
				newValue = convert((String) value, type);
			} else if (value instanceof String[]) {
				newValue = convert(((String[]) value)[0], type);
			} else {
				newValue = convert(value, type);
			}
		}

		// Invoke the setter method
		try {
			getPropertyUtils().setProperty(target, name, newValue);
		} catch (NoSuchMethodException e) {
			throw new InvocationTargetException(e, "Cannot set " + propName);
		}

	}

	private static PropertyUtilsBean getPropertyUtils() {
		if (propertyUtilsBean == null) {
			propertyUtilsBean = new PropertyUtilsBean();
		}
		return propertyUtilsBean;
	}

}
